﻿(function($, undefined) {
  /*
   * Unobtrusive scripting adapter for jQuery, largely taken from
   * the wonderful https://github.com/rails/jquery-ujs
   *
   *
   * Released under the MIT license
   *
   */

    String.prototype.reverse = function() {
        return this.split('').reverse().join('');
    };
    var web2py;

    $.web2py = web2py = {

        popup: function(url) {
            newwindow = window.open(url, 'name', 'height=400,width=600');
            if (window.focus) newwindow.focus();
            return false;
        },

        collapse: function(id) {
            $('#' + id).slideToggle();
        },

        fade: function(id, value) {
            if (value > 0) $('#' + id).hide().fadeIn('slow');
            else $('#' + id).show().fadeOut('slow');
        },

        ajax: function(u, s, t) {
            query = '';
            if (typeof s == 'string') {
                d = $(s).serialize();
                if (d) {
                    query = d;
                }
            } else {
                pcs = [];
                if (s != null && s != undefined) for (i = 0; i < s.length; i++) {
                    q = $('[name=' + s[i] + ']').serialize();
                    if (q) {
                        pcs.push(q);
                    }
                }
                if (pcs.length > 0) {
                    query = pcs.join('&');
                }
            }
            $.ajax({
                type: 'POST',
                url: u,
                data: query,
                success: function(msg) {
                    if (t) {
                        if (t == ':eval') eval(msg);
                        else if (typeof t == 'string') $('#' + t).html(msg);
                        else t(msg);
                    }
                }
            });
        },

        ajax_fields: function(target) {
            /*this attaches something to a newly loaded fragment/page
            * Ideally all events should be bound to the document, so we can avoid calling
            * this over and over... all will be bound to the document
            */
// Eden doesn't use Web2Py Calendar
//          var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d";
//          var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S";
//          $('input.date', target).each(function() {
//              Calendar.setup({
//                  inputField: this,
//                  ifFormat: date_format,
//                  showsTime: false
//              });
//          });
//          $('input.datetime', target).each(function() {
//              Calendar.setup({
//                 inputField: this,
//                 ifFormat: datetime_format,
//                 showsTime: true,
//                 timeFormat: '24'
//              });
//         });
//         $(input.time', target).each(function() {
//             $(this).timeEntry();
//         });
            /* adds btn class to buttons */
            $('button', target).addClass('btn');
            $('form input[type="submit"], form input[type="button"]', target).addClass('btn');
            /* no more inline javascript for PasswordWidget */
            $('input[type=password][data-w2p_entropy]', target).each(function() {
                web2py.validate_entropy($(this));
            });
            /* no more inline javascript for ListWidget */
            $('ul.w2p_list', target).each(function() {
                function pe(ul, e) {
                    var new_line = ml(ul);
                    rel(ul);
                    if ($(e.target).parent().is(':visible')) {
                        //make sure we didn't delete the element before we insert after
                        new_line.insertAfter($(e.target).parent());
                    } else {
                        //the line we clicked on was deleted, just add to end of list
                        new_line.appendTo(ul);
                    }
                    new_line.find(":text").focus();
                    return false;
                }

                function rl(ul, e) {
                    if ($(ul).children().length > 1) {
                        //only remove if we have more than 1 item so the list is never empty
                        $(e.target).parent().remove();
                    }
                }

                function ml(ul) {
                    var line = $(ul).find('li:first').clone(true);
                    line.find(':text').val('');
                    return line;
                }

                function rel(ul) {
                    $(ul).find('li').each(function() {
                        var trimmed = $.trim($(this.firstChild).val());
                        if (trimmed == '') $(this).remove();
                        else $(this.firstChild).val(trimmed);
                    });
                }
                var ul = this;
                $(ul).find(':text').after('<a href="#">+</a>&nbsp;<a href="#">-</a>').keypress(function(e) {
                    return(e.which == 13) ? pe(ul, e) : true;
                }).next().click(function(e) {
                    pe(ul, e);
                    e.preventDefault();
                }).next().click(function(e) {
                    rl(ul, e);
                    e.preventDefault();
                });
            });
        },

        ajax_init: function(target) {
            $('.hidden', target).hide();
            web2py.manage_errors(target);
            web2py.ajax_fields(target);
            web2py.show_if_handler(target);
            web2py.component_handler(target);
        },

        //manage errors in forms
        manage_errors: function(target) {
            $('.error', target).hide().slideDown('slow');
            //$('.error', target).hide().fadeIn('slow');
        },

        event_handlers: function() {
            /* This is called once for page
             * Ideally it should bound all the things that are needed
             */
            var doc = $(document);
            doc.on('click', '.flash', function(e) {
                var t = $(this);
                if (t.css('top') == '0px') t.slideUp('slow');
                else t.fadeOut();
                //if I want to display a clickable something
                //inside flash, I should not be prevented to follow it
                //e.preventDefault();
            });
            doc.on('keyup', 'input.integer', function() {
                this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse();
            });
            doc.on('keyup', 'input.double, input.decimal', function() {
                this.value = this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g, '').reverse();
            });
            var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?";
            doc.on('click', "input[type='checkbox'].delete", function() {
                if (this.checked) if (!web2py.confirm(confirm_message)) this.checked = false;
            });

            doc.ajaxSuccess(function(e, xhr) {
                var redirect = xhr.getResponseHeader('web2py-redirect-location');
                var command = xhr.getResponseHeader('web2py-component-command');
                var flash = xhr.getResponseHeader('web2py-component-flash');
                if (redirect !== null) {
                    window.location = redirect;
                };
                if (command !== null) {
                    eval(decodeURIComponent(command));
                }
                if (flash) {
                    web2py.flash(decodeURIComponent(flash))
                }
            });

            doc.ajaxError(function(e, xhr, settings, exception) {
                //personally I don't like it.
                //if there's an error it flashed and can be removed
                //as any other message
                //doc.off('click', '.flash')
                switch(xhr.status) {
                case 500:
                  web2py.flash(i18n.ajax_500);
                }
            });
        },

        trap_form: function(action, target) {
            $('#' + target + ' form').each(function(i) {
                var form = $(this);
                form.attr('data-w2p_target', target);
                if (!form.hasClass('no_trap')) {
                    //should be there by default ?
                    form.find('input[type=submit]').attr('data-w2p_disable_with', 'Working...');
                    form.submit(function(e) {
                        web2py.hide_flash();
                        web2py.ajax_page('post', action, form.serialize(), target, form);
                        e.preventDefault();
                    });
                }
            });
        },

        trap_link: function(target) {
            $('#' + target + ' a.w2p_trap').each(function(i) {
                var link = $(this);
                link.click(function(e) {
                    web2py.hide_flash();
                    web2py.ajax_page('get', link.attr('href'), [], target);
                    e.preventDefault();
                });
            });
        },

        ajax_page: function(method, action, data, target, element) {
            //element is a new parameter, but should be put be put in front
            if (element == undefined) element = $(document);
            if (web2py.fire(element, 'ajax:before')) { //test a usecase, should stop here if returns false
                $.ajax({
                    'type': method,
                    'url': action,
                    'data': data,
                    'beforeSend': function(xhr, settings) {
                        //added
                        xhr.setRequestHeader('web2py-component-location', document.location);
                        xhr.setRequestHeader('web2py-component-element', target);
                        return web2py.fire(element, 'ajax:beforeSend', [xhr, settings]); //test a usecase, should stop here if returns false
                    },
                    //added
                    'success': function(data, status, xhr) {
                        //bummer for form submissions....the element is not there after complete
                        //because it gets replaced by the new response....
                        element.trigger('ajax:success', [data, status, xhr]);
                    },
                    //added
                    'error': function(xhr, status, error) {
                        //bummer for form submissions....in addition to the element being not there after
                        //complete because it gets replaced by the new response, standard form
                        //handling just returns the same status code for good and bad
                        //form submissions (i.e. that triggered a validator error)
                        element.trigger('ajax:error', [xhr, status, error]);
                    },
                    'complete': function(xhr, status) {
                        element.trigger('ajax:complete', [xhr, status]);
                        var html = xhr.responseText;
                        var content = xhr.getResponseHeader('web2py-component-content');
                        var t = $('#' + target);
                        if (content == 'prepend') t.prepend(html);
                        else if (content == 'append') t.append(html);
                        else if (content != 'hide') t.html(html);
                        web2py.trap_form(action, target);
                        web2py.trap_link(target);
                        web2py.ajax_init('#' + target);
                    }
                });
            }
        },

        component: function(action, target, timeout, times, el) {
            //element is a new parameter, but should be put in front
            $(function() {
                var jelement = $("#" + target);
                var element = jelement.get(0);
                var statement = "jQuery('#" + target + "').get(0).reload();";
                element.reload = function() {
                    // Continue if times is Infinity or
                    // the times limit is not reached
                    if (element.reload_check()) {
                        web2py.ajax_page('get', action, null, target, el);
                    }
                }; // reload
                // Method to check timing limit
                element.reload_check = function() {
                    if (jelement.hasClass('w2p_component_stop')) {
                        clearInterval(this.timing);
                        return false;
                    }
                    if (this.reload_counter == Infinity) {
                        return true;
                    } else {
                        if (!isNaN(this.reload_counter)) {
                            this.reload_counter -= 1;
                            if (this.reload_counter < 0) {
                                if (!this.run_once) {
                                    clearInterval(this.timing);
                                    return false;
                                }
                            } else {
                                return true;
                            }
                        }
                    }
                    return false;
                }; // reload check
                if (!isNaN(timeout)) {
                    element.timeout = timeout;
                    element.reload_counter = times;
                    if (times > 1) {
                        // Multiple or infinite reload
                        // Run first iteration
                        web2py.ajax_page('get', action, null, target, el);
                        element.run_once = false;
                        element.timing = setInterval(statement, timeout);
                        element.reload_counter -= 1;
                    } else if (times == 1) {
                        // Run once with timeout
                        element.run_once = true;
                        element.setTimeout = setTimeout;
                        element.timing = setTimeout(statement, timeout);
                    }
                } else {
                  // run once (no timeout specified)
                  element.reload_counter = Infinity;
                  web2py.ajax_page('get', action, null, target, el);
                }
            });
        },

        calc_entropy: function(mystring) {
            //calculate a simple entropy for a given string
            var csets = new Array(
                'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                '0123456789', '!@#$\%^&*()', '~`-_=+[]{}\|;:\'",.<>?/',
                '0123456789abcdefghijklmnopqrstuvwxyz');
            var score = 0,
                other = {}, seen = {}, lastset = null,
                mystringlist = mystring.split('');
            for (var i = 0; i < mystringlist.length; i++) { // classify this character
                var c = mystringlist[i],
                    inset = 5;
                for (var j = 0; j < csets.length; j++)
                    if (csets[j].indexOf(c) != -1) {
                        inset = j;
                        break;
                    }
                    //calculate effect of character on alphabet size
                    if (!(inset in seen)) {
                        seen[inset] = 1;
                        score += csets[inset].length;
                    } else if (!(c in other)) {
                        score += 1;
                        other[c] = 1;
                    }
                    if (inset != lastset) {
                        score += 1;
                        lastset = inset;
                    }
            }
            var entropy = mystring.length * Math.log(score) / 0.6931471805599453;
            return Math.round(entropy * 100) / 100
        },

        validate_entropy: function(myfield, req_entropy) {
            //added
            if (myfield.data('w2p_entropy') != undefined) req_entropy = myfield.data('w2p_entropy');
            var validator = function() {
                var v = (web2py.calc_entropy(myfield.val()) || 0) / req_entropy;
                var r = 0,
                    g = 0,
                    b = 0,
                    rs = function(x) {
                        return Math.round(x * 15).toString(16)
                    };
                if (v <= 0.5) {
                    r = 1.0;
                    g = 2.0 * v;
                } else {
                    r = (1.0 - 2.0 * (Math.max(v, 0) - 0.5));
                    g = 1.0;
                }
                var color = '#' + rs(r) + rs(g) + rs(b);
                myfield.css('background-color', color);
                entropy_callback = myfield.data('entropy_callback');
                if (entropy_callback) entropy_callback(v);
            }
            if (!myfield.hasClass('entropy_check')) myfield.on('keyup', validator).on('keydown', validator).addClass('entropy_check');
        },

        web2py_websocket: function(url, onmessage, onopen, onclose) {
            if ("WebSocket" in window) {
                var ws = new WebSocket(url);
                ws.onopen = onopen ? onopen : (function() {});
                ws.onmessage = onmessage;
                ws.onclose = onclose ? onclose : (function() {});
                return true; // supported
            } else return false; // not supported
        },

        /* new from here */
        // Form input elements bound by jquery-ujs
        formInputClickSelector: 'input[type=submit], input[type=image], button[type=submit], button:not([type])',

        // Form input elements disabled during form submission
        disableSelector: 'input, button, textarea, select',

        // Form input elements re-enabled after form submission
        enableSelector: 'input:disabled, button:disabled, textarea:disabled, select:disabled',

        // Triggers an event on an element and returns false if the event result is false
        fire: function(obj, name, data) {
            var event = $.Event(name);
            obj.trigger(event, data);
            return event.result !== false;
        },

        // Helper function, needed to provide consistent behavior in IE
        stopEverything: function(e) {
            $(e.target).trigger('w2p:everythingStopped');
            e.stopImmediatePropagation();
            return false;
        },

        confirm: function(message) {
            return confirm(message);
        },

        // replace element's html with the 'data-disable-with' after storing original html
        // and prevent clicking on it
        disableElement: function(el) {
            el.addClass('disabled');
            var method = el.prop('type') == 'submit' ? 'val' : 'html';
            // store enabled state
            el.data('w2p:enable-with', el[method]);
            /* little addition by default*/
            if ((el.data('w2p_disable_with') == 'default') || (el.data('w2p_disable_with') === undefined)) {
                el.data('w2p_disable_with', 'Working...');
            }
            // set to disabled state
            el[method](el.data('w2p_disable_with'));

            el.bind('click.w2pDisable', function(e) { // prevent further clicking
                return web2py.stopEverything(e);
            });
        },

        // restore element to its original state which was disabled by 'disableElement' above
        enableElement: function(el) {
            var method = el.prop('type') == 'submit' ? 'val' : 'html';
            if (el.data('w2p:enable-with') !== undefined) {
                // set to old enabled state
                el[method](el.data('w2p:enable-with'));
                el.removeData('w2p:enable-with'); // clean up cache
            }
            el.removeClass('disabled');
            el.unbind('click.w2pDisable'); // enable element
        },

        //convenience wrapper, internal use only
        simple_component: function(action, target, element) {
            web2py.component(action, target, 0, 1, element);
        },

        //helper for flash messages
        flash: function(message, status) {
            var flash = $('.flash');
            web2py.hide_flash();
            flash.html(message).addClass(status);
            if (flash.html()) flash.append('<span id="closeflash"> &times; </span>').slideDown();
        },

        hide_flash: function() {
            $('.flash').hide().html('');
        },

        show_if_handler: function(target) {
            var triggers = {};
            var show_if = function() {
                var t = $(this);
                var id = t.attr('id');
                t.attr('value', t.val());
                for (var k = 0; k < triggers[id].length; k++) {
                    var dep = $('#' + triggers[id][k], target);
                    var tr = $('#' + triggers[id][k] + '__row', target);
                    if (t.is(dep.attr('data-show-if'))) tr.slideDown();
                    else tr.hide();
                }
            };
            $('[data-show-trigger]', target).each(function() {
                var name = $(this).attr('data-show-trigger');
                if (!triggers[name]) triggers[name] = [];
                triggers[name].push($(this).attr('id'));
            });
            for (var name in triggers) {
                $('#' + name, target).change(show_if).keyup(show_if);
                show_if.call($('#' + name, target));
            };
        },
        component_handler : function(target) {
            $('div[data-w2p_remote]', target).each(function() {
                var remote, times, timeout, target;
                var el = $(this);
                remote = el.data('w2p_remote');
                times = el.data('w2p_times');
                timeout = el.data('w2p_timeout');
                target = el.attr('id');
                web2py.component(remote, target, timeout, times, $(this));
            });
        },
        a_handler: function(el, e) {
            e.preventDefault();
            var method = el.data('w2p_method');
            var action = el.attr('href');
            var target = el.data('w2p_target');
            var confirm_message = el.data('w2p_confirm');

            var pre_call = el.data('w2p_pre_call');
            if (pre_call != undefined) {
                eval(pre_call);
            }
            if (confirm_message != undefined) {
                if (confirm_message == 'default') confirm_message = i18n.delete_confirmation;
                if (!web2py.confirm(confirm_message)) {
                    web2py.stopEverything(e);
                    return;
                }
            }
            if (target == undefined) {
                if (method == 'GET') {
                    web2py.simple_component(action, el.attr('id'), el); //not working with original
                } else if (method == 'POST') {
                    //should be web2py.ajax(action, [], ''); but it's too simple
                    web2py.ajax_page('post', action, [], 'bogus', el); //fixme?
                }
            } else {
                if (method == 'GET') {
                    web2py.simple_component(action, target, el);
                } else if (method == 'POST') {
                    //should be web2py.ajax(action, [], target); but it's too simple
                    web2py.ajax_page('post', action, [], target, el);
                }
            }
            /*this should happen only on ajaxsuccess
             * and should block subsequent clicks until ajaxcomplete
             * NB: introduce the first incompatibility because normally
             * the element would be removed in either case
            /* removal code moved to the ajax:success event - START
            if (toremove != undefined) {
                toremove = el.closest(toremove);
                if (!toremove.length) {
                    //this enables removal of whatever selector if a closest is not found
                    toremove = $(toremove);
                }
                toremove.remove();
            }
            /* removal code moved to the ajax:success event - END */
        },

        a_handlers: function() {
            var el = $(document);
            el.on('click', 'a[data-w2p_method]', function(e) {
                web2py.a_handler($(this), e);
            });
            /* removal of element should happen only on success */
            el.on('ajax:success', 'a[data-w2p_method][data-w2p_remove]', function(e) {
                var el = $(this);
                var toremove = el.data('w2p_remove');
                if (toremove != undefined) {
                    toremove = el.closest(toremove);
                    if (!toremove.length) {
                        //this enables removal of whatever selector if a closest is not found
                        toremove = $(toremove);
                    }
                    toremove.remove();
                }
            });
            el.on('ajax:beforeSend', 'a[data-w2p_method][data-w2p_disable_with]', function(e) {
                web2py.disableElement($(this));
            });
            /*re-enable click on completion*/
            el.on('ajax:complete', 'a[data-w2p_method][data-w2p_disable_with]', function(e) {
                web2py.enableElement($(this));
            });
        },

        /* Disables form elements:
            - Caches element value in 'ujs:enable-with' data store
            - Replaces element text with value of 'data-disable-with' attribute
            - Sets disabled property to true
        */
        disableFormElements: function(form) {
            form.find(web2py.disableSelector).each(function() {
                var element = $(this), method = element.is('button') ? 'html' : 'val';
                var disable_with = element.data('w2p_disable_with');
                if (disable_with == undefined) {
                    element.data('w2p_disable_with', element[method]())
                }
                element.data('w2p:enable-with', element[method]());
                element[method](element.data('w2p_disable_with'));
                element.prop('disabled', true);
            });
        },

        /* Re-enables disabled form elements:
            - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
            - Sets disabled property to false
        */
        enableFormElements: function(form) {
            form.find(web2py.enableSelector).each(function() {
                var element = $(this), method = element.is('button') ? 'html' : 'val';
                if (element.data('w2p:enable-with')) element[method](element.data('w2p:enable-with'));
                element.prop('disabled', false);
            });
        },

        form_handlers: function() {
            var el = $(document);
            el.on('ajax:beforeSend', 'form[data-w2p_target]', function(e) {
                web2py.disableFormElements($(this));
            });
            el.on('ajax:complete', 'form[data-w2p_target]', function(e) {
                web2py.enableFormElements($(this));
            });
        }
    }

    //end of functions
    //main hook
    $(function() {
        var flash = $('.flash');
        flash.hide();
        if (flash.html()) web2py.flash(flash.html());
        web2py.ajax_init(document);
        web2py.event_handlers();
        web2py.a_handlers();
        web2py.form_handlers();
    });

})(jQuery);

/* compatibility code - start */
ajax = $.web2py.ajax;
web2py_component = $.web2py.component;
web2py_websocket = $.web2py.websocket;
web2py_ajax_page = $.web2py.ajax_page;
//needed for IS_STRONG(entropy)
web2py_validate_entropy = $.web2py.validate_entropy;
//needed for crud.search and SQLFORM.grid's search
web2py_ajax_fields = $.web2py.ajax_fields;
//used for LOAD(ajax=False)
web2py_trap_form = $.web2py.trap_form;

/* undocumented - rare */
popup = $.web2py.popup;
collapse = $.web2py.collapse;
fade = $.web2py.fade;

/* internals - shouldn't be needed

web2py_ajax_init = $.web2py.ajax_init;
web2py_event_handlers = $.web2py.event_handlers;

web2py_trap_link = $.web2py.trap_link;
web2py_calc_entropy = $.web2py.calc_entropy;
*/
/* compatibility code - end*/
